package flexo.file.reader;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

import flexo.file.Node;
import flexo.file.reader.Reader;
import flexo.math.Vector;


public class Obj extends Reader
{
	enum Info
	{
		VERTEX,
		TEXTURE,
		NORMAL,
	}
	
	enum Token
	{
		EOF,
		STRING,
		NUMBER,
		ERROR
	}
	
	protected BufferedReader sr;

	protected String lexeme;
	protected char current;
	protected boolean eof;
	protected Token token;
	protected int line;
	
	protected void readChar() throws Exception
	{
		boolean comment = false;
		do
		{
			int temp = sr.read();
			if (temp == -1)
			{
				eof = true;
				return;
			}
			current = (char)temp;
			if (current == '\n')
			{
				++line;
				comment = false;
			}
			else if (current == '#')
			{
				comment = true;
			}
		} 
		while (isIgnoredChar(current) || comment);		
	}
	
	public boolean isValidChar(char c)
	{
		return Character.isLetterOrDigit(c) 
		|| c == '.' || c == '-' || c == '_';
	}
	
	protected boolean isIgnoredChar(char c)
	{
		return Character.isWhitespace(current) || current == '/';
	}
	
	public Token lexNextToken() throws Exception
	{
		char character = current;
		lexeme = new String();

		if (eof)
		{
			return Token.EOF;
		}

		//readChar();
		
		if (isValidChar(character))
		{
			//lexeme += character;
			while (isValidChar(current))
			{
				lexeme += current;
				try
				{
					current = (char)sr.read();
				}
				catch (IOException e)
				{
					e.printStackTrace();
				}
			}
			if (isIgnoredChar(current))
			{
				readChar();
			}
			try
			{
				Float.parseFloat(lexeme);
				return Token.NUMBER;
			}
			catch(Exception e)
			{
				
			}
			return Token.STRING;
		}
		return Token.ERROR;
	}
	
	protected void nextToken() throws Exception
	{
		token = lexNextToken();
	}

	protected void match(Token token) throws Exception
	{
		if (this.token != token)
		{
			throw new Exception();
		}
		nextToken();
	}

	public void parse(Node node) throws Exception
	{
		line = 1;
		
		ArrayList<Vector> v = new ArrayList<Vector>();
		ArrayList<Vector> vn = new ArrayList<Vector>();
		ArrayList<Vector> vt = new ArrayList<Vector>();
		
		ArrayList<Vector> f = new ArrayList<Vector>(); 
		
		nextToken();
		while (token != Token.EOF)
		{
			String command = lexeme;
			
			// Vertex
			if (command.equals("v"))
			{
				vertex(v,Info.VERTEX);
			}
			// Vertex normal
			else if (command.equals("vn"))
			{
				vertex(vn,Info.NORMAL);
			}
			// Texture coords
			else if (command.equals("vt"))
			{
				vertex(vt,Info.TEXTURE);
			}
			// Face
			else if (command.equals("f"))
			{
				face(f);
			}
			else
			{
 				if (token == Token.STRING)
				{
					match(Token.STRING);
				}
 				else if (token == Token.NUMBER)
 				{
 					match(Token.NUMBER);
 				}
				else
				{
					throw new Exception("Error loading model in line " + line);
				}
			}
		}
		
		// Process the info and create the real file structure
		/*
		 * vertices
		 *    + vertex: x y z u v w nx ny nz
		 * indices
		 *    + index: value
		 */
		
		Node vertices = node.addChild("vertices");
		Node indices = node.addChild("indices");
		int vertexCount = 0;
		int indexCount = 0;
		
		java.util.HashMap<String,Node> vertexMap = new HashMap<String, Node>(); 
		
		for (Vector poly : f)
		{
			int iV,iVT,iVN;
			iV = ((int)poly.x)-1;
			iVT = ((int)poly.y)-1;
			iVN = ((int)poly.z)-1;
			
			String vertexID = iV + "," + iVT + "," + iVN;
			Node vertexNode = vertexMap.get(vertexID);
			if (vertexNode == null)
			{
				vertexNode = vertices.addChild(String.valueOf(vertexCount++));
				
				Vector vert = v.get(iV);
				Vector vertT = vt.get(iVT);
				Vector vertN = vn.get(iVN);
				
				vertexNode.setProperty("x", vert.x);
				vertexNode.setProperty("y", vert.y);
				vertexNode.setProperty("z", vert.z);
				
				vertexNode.setProperty("u", vertT.x);
				vertexNode.setProperty("v", vertT.y);
				vertexNode.setProperty("w", vertT.z);
				
				vertexNode.setProperty("nx", vertN.x);
				vertexNode.setProperty("ny", vertN.y);
				vertexNode.setProperty("nz", vertN.z);
				
				vertexMap.put(vertexID, vertexNode);
			}
			Node indexNode = indices.addChild(String.valueOf(indexCount++));
			indexNode.setProperty("value", Integer.parseInt(vertexNode.name));
		}
	}
	
	/**
	 * Each face is made by 3 vertices. For example, the first polygon is made
	 * using the vertices in position 0,1 and 2 of the list.
	 */
	public void face(ArrayList<Vector> list) throws Exception
	{
		match(Token.STRING);
		
		float xf,yf,zf;
		
		// First vertex		
		
		xf = Float.parseFloat(lexeme);
		match(Token.NUMBER);
		
		yf = Float.parseFloat(lexeme);
		match(Token.NUMBER);
		
		zf = Float.parseFloat(lexeme);
		match(Token.NUMBER);
		
		Vector v1 = new Vector(xf,yf,zf); 
		list.add(v1);
		
		// Second vertex		
		
		xf = Float.parseFloat(lexeme);
		match(Token.NUMBER);
		
		yf = Float.parseFloat(lexeme);
		match(Token.NUMBER);
		
		zf = Float.parseFloat(lexeme);
		match(Token.NUMBER);
		
		Vector v2 = new Vector(xf,yf,zf); 
		list.add(v2);
		
		// Third vertex		
		
		xf = Float.parseFloat(lexeme);
		match(Token.NUMBER);
		
		yf = Float.parseFloat(lexeme);
		match(Token.NUMBER);
		
		zf = Float.parseFloat(lexeme);
		match(Token.NUMBER);
		
		Vector v3 = new Vector(xf,yf,zf); 
		list.add(v3);
		
		// If it's a quad we have to make another triangle
		// using the first and third vertices
		if (token == Token.NUMBER)
		{
			// Fourth vertex		
			
			xf = Float.parseFloat(lexeme);
			match(Token.NUMBER);
			
			yf = Float.parseFloat(lexeme);
			match(Token.NUMBER);
			
			zf = Float.parseFloat(lexeme);
			match(Token.NUMBER);
			
			Vector v4 = new Vector(xf,yf,zf);
			list.add(v1);
			list.add(v3);
			list.add(v4);
		}
	}
	
	public void vertex(ArrayList<Vector> list, Info type) throws Exception
	{
		match(Token.STRING);
		
		float xf = Float.parseFloat(lexeme);
		match(Token.NUMBER);
		
		float yf = Float.parseFloat(lexeme);
		match(Token.NUMBER);
		
		float zf;
		try
		{
			zf = Float.parseFloat(lexeme);
			match(Token.NUMBER);
		}
		catch (Exception e)
		{
			zf = 0;
		}
		
		
		list.add(new Vector(xf,yf,zf));
	}	

	public void read(Node fileNode)
	{
		try
		{
			sr = this.getBufferedReader(fileNode);
			readChar();
			parse(fileNode);
			
			sr.close();
		}
		catch (Exception e)
		{
			formatError(fileNode.name,line);
		}
	}
}
